/**
 * Created by yuzhang on 2018/4/26.
 * 可对组件内的任意函数增加功能，例如打点
 */

import { forIn } from './utils'

// 在原型方法上绑定hooks操作
// 如果不考虑追踪class内部定义的箭头函数，可以直接使用这个方法
export const ba = methodHooks => InnerComponent => {
  let ref = InnerComponent.prototype
  forIn(methodHooks, (value, key) => {
    let cache = ref.key
    ref[key] = function (...args) {
      value()
      return cache && cache.apply(this, args)
    }
  })
  return InnerComponent
}

// 在指定的生命周期函数中，执行绑定hooks操作（为了可以追踪箭头函数）
// 一般时机在组件将要加载时hook，即componentWillMount
export const base = lifeCircleFnName => methodHooksFunc => InnerComponent => {
  // 引用目标组件原型
  let ref = InnerComponent.prototype // 组件原型
  let refLCFnCache = ref[lifeCircleFnName]
  // 在该生命周期方法执行时添加hooks方法
  ref[lifeCircleFnName] = function (...args) { // 修改这个生命周期函数
    // 进入这里 this 指的是被 hook 的组件(或可称为‘类’) InnerComponent 的某个实例
    const methodHooks = methodHooksFunc(this) // methodHooksFunc 是一个方法，传入组件实例，为生成的 hook 方法提供更多操作空间
    // 循环给所有要hook的方法添加hook，这个步骤只需要在组件每个实例中执行一次就行
    // 如果选择的时机即该生命周期方法，会调用多次，那么在完毕之后应恢复该生命周期函数lifeCircleFnName
    forIn(methodHooks, (value, key) => {
      let cache = this[key] // 原始方法
      this[key] = function (...args) { // 修改原始方法
        value() // 在执行原始方法前执行自定义方法
        return cache && cache.apply(this, args) // 执行原始方法
      }
    }) // hook 完毕
    // 如果 methodHooks 中包含 lifeCircleFnName，一般是componentWillMount
    // 那么被修改后lifeCircleFnName方法执行到这里时，检查下，执行自定义的lifeCircleFnName方法
    if (methodHooks[lifeCircleFnName]) {
      // 如果存在被hook的函数
      methodHooks[lifeCircleFnName]()
      // 在实例中恢复被修改的方法
      // 加上自定义方法
      this[lifeCircleFnName] = function (...args) {
        methodHooks[lifeCircleFnName]()
        return refLCFnCache && refLCFnCache.apply(this, args)
      }
    } else {
      // 在实例中恢复被修改的方法
      // 直接恢复之前的函数
      this[lifeCircleFnName] = refLCFnCache
    }
    // 好了，hook 完毕了，后事也处理完了，现在该执行原始的方法了lifeCircleFnName
    return refLCFnCache && refLCFnCache.apply(this, args)
  }
  return InnerComponent // 返回修改了原型(prototype)后的组件，皆大欢喜😄！
}

// 在 componentWillMount 上执行绑定操作
export const trackCWMHOC = base('componentWillMount')
